using System;
using System.Collections.Generic;
using System.Text;
using NBusiness.CodeDom;
using NBusiness.ESharp.Compiler;
using NBusiness.CodeDom.Compiler;

namespace NBusiness.ESharp
{
    [Serializable]
    public class ESharpRelationship : EntityRelationship
    {
        #region Find relative
        private Entity FindRelative(Entity entity, EntityElement[] relative)
        {
            Entity relativeEntity = null;

            //Find relative to this entity
            IFamilyUnit fu = entity.Parent as IFamilyUnit;
            foreach (EntityElement path in relative)
            {
                if (fu.Families.ContainsKey(path.Value))
                {
                    fu = fu.Families[path.Value];
                }
                else if (fu is EntityFamily)
                {
                    relativeEntity = (fu as EntityFamily).Entities.Find(delegate(Entity e)
                    {
                        return e.Name == path.Value;
                    });
                    if (relativeEntity != null) break;
                }
            }

            //Find relative to root, we always do both in order to find ambiguous references too

            Entity rootEntity = null;

            fu = entity.Parent as IFamilyUnit;
            while (fu.Parent != null) fu = fu.Parent as IFamilyUnit;

            if (relative.Length > 1)
            {
                foreach (EntityElement path in relative)
                {
                    if (!fu.Families.ContainsKey(path.Value) && fu is EntityFamily)
                    {
                        rootEntity = (fu as EntityFamily).Entities.Find(delegate(Entity e)
                        {
                            return e.Name == path.Value;
                        });
                        if (rootEntity != null) break;
                    }
                    else fu = fu.Families[path.Value];
                }
            }

            Entity result = (relativeEntity == null ? rootEntity : relativeEntity);

            return result;
        }
        #endregion
        
        protected internal override void Attach()
        {
            List<EntityElement> entityPath = new List<EntityElement>();
            EntityField assign = null;

            foreach (EntityElement el in Elements)
            {
                switch (el.Type)
                {
                    case EntityElementType.RelationshipEntity:
                        entityPath.Add(el);
                        break;
                    case EntityElementType.RelationshipAs:
                    case EntityElementType.RelationshipOn:
                        if(With == null)
                        {
                            //Find the entity this relationship is with.
                            Entity entity = FindRelative(From, entityPath.ToArray());
                            if (entity != null)
                            {
                                With = entity;

                                //with and from should both reference this relationship
                                With.Relationships.Add(this);
                            }
                            else OnBuildError(new ESharpBuildError(ESharpErrorCode.ES3001, el));
                        }
                        if (Type == EntityRelationshipType.Sibling && el.Type == EntityElementType.RelationshipOn)
                            OnBuildError(new ESharpBuildError(ESharpErrorCode.ES3002, el));

                        break;
                    case EntityElementType.RelationshipField:
                        {
                            EntityField[] fields = (From as ESharpEntity).Fields.FindAll(new Predicate<EntityField>(
                                delegate(EntityField field)
                                {
                                    return field.Name == el.Value;
                                })).ToArray();

                            if (fields.Length == 0 && Type != EntityRelationshipType.Sibling)
                                OnBuildError(new ESharpBuildError(ESharpErrorCode.ES3003, el));
                            else if (fields.Length > 1 && Type != EntityRelationshipType.Sibling)
                                OnBuildError(new ESharpBuildError(ESharpErrorCode.ES3009, el));
                            else
                                assign = fields[0];

                            break;
                        }
                    case EntityElementType.RelationshipAssignTo:
                        {
                            if (With != null)
                            {
                                EntityField[] fields = (With as ESharpEntity).Fields.FindAll(new Predicate<EntityField>(
                                    delegate(EntityField field)
                                    {
                                        return field.Name == el.Value;
                                    })).ToArray();

                                EntityField to = null;

                                if (fields.Length == 0 && Type != EntityRelationshipType.Sibling)
                                    OnBuildError(new ESharpBuildError(ESharpErrorCode.ES3003, el));
                                else if (fields.Length > 1 && Type != EntityRelationshipType.Sibling)
                                    OnBuildError(new ESharpBuildError(ESharpErrorCode.ES3009, el));
                                else
                                    to = fields[0];

                                if (to != null && assign != null)
                                {
                                    On.Add(assign, to);
                                }
                            }
                            else OnBuildError(new ESharpBuildError(ESharpErrorCode.ES0005, el));
                            break;
                        }
                    default: break;
                }
            }
            

            switch (this.Type)
            {
                case EntityRelationshipType.Parent:
                    if (On.Count == 0)
                        OnBuildError(new ESharpBuildError(ESharpErrorCode.ES3000, Elements[0]));
                    
                    foreach (EntityField on in On.Keys)
                    {
                        EntityField to = On[on];
                        if (to != null && (on.IsId || !to.IsId))
                            OnBuildError(new ESharpBuildError(ESharpErrorCode.ES3004, Elements[0]));
                    }
                    break;
                case EntityRelationshipType.Child:
                    if (On.Count == 0)
                        OnBuildError(new ESharpBuildError(ESharpErrorCode.ES3005, Elements[0]));

                    foreach (EntityField on in On.Keys)
                    {
                        EntityField to = On[on];
                        if (to != null && (!on.IsId || to.IsId))
                            OnBuildError(new ESharpBuildError(ESharpErrorCode.ES3006, Elements[0]));
                    }
                    break;
                case EntityRelationshipType.Sibling:

                    bool attached = true;
                    EntityRelationship backSibling = null;
                    if (With != null)
                    {
                        backSibling = With.Relationships.Find(new Predicate<EntityRelationship>(
                        delegate(EntityRelationship r)
                        {
                            if (r.With == null) attached = false;
                            return r.Type == EntityRelationshipType.Sibling && r.From == With && r.With == From;
                        }));
                    }

                    if (attached && backSibling == null)
                        OnBuildError(new ESharpBuildError(ESharpErrorCode.ES3007, Elements[0]));

                    break;
            }

            foreach (EntityField field in On.Keys)
            {
                EntityField onField = On[field];
                
                if (onField != null)
                {
                    Type t1 = (onField.IsNullable ? Nullable.GetUnderlyingType(onField.Type) : onField.Type);
                    Type t2 = (field.IsNullable ? Nullable.GetUnderlyingType(field.Type) : field.Type);

                    if (t1 != t2)
                    {
                        EntityElement el = Elements.Find(new Predicate<EntityElement>(delegate(EntityElement e)
                        {
                            return e.Type == EntityElementType.RelationshipField;
                        }));

                        if (el == null) el = Elements[0];

                        OnBuildError(new ESharpBuildError(ESharpErrorCode.ES3008, el));
                    }
                }
            }
        }

        protected override void InitializeAssignmentLogic()
        {
            //[declaration] [with] [entity] [on] [field][assign][to][split] [as] [type][end]
            //[declaration] [with] [entity] [as] [type][end]

            //Assign name
            Assignments.Add(new AssignmentLogic(
                ESharpParser.Expressions.Identifier,
                EntityElementType.RelationshipName,
                new AssignmentLogicHandler(AssignName),
                EntityElementType.RelationshipDeclaration));

            //with
            Assignments.Add(new AssignmentLogic(
                ESharpParser.Expressions.RelationshipWith,
                EntityElementType.RelationshipWith,
                EntityElementType.RelationshipName));
            
            //entity
            Assignments.Add(new AssignmentLogic(
                ESharpParser.Expressions.Identifier,
                EntityElementType.RelationshipEntity,
                EntityElementType.RelationshipWith));

            //on
            Assignments.Add(new AssignmentLogic(
                ESharpParser.Expressions.RelationshipOn,
                EntityElementType.RelationshipOn,
                EntityElementType.RelationshipEntity));

            //field
            Assignments.Add(new AssignmentLogic(
                ESharpParser.Expressions.Identifier,
                EntityElementType.RelationshipField,
                EntityElementType.RelationshipOn,
                EntityElementType.RelationshipSplit));

            //assign
            Assignments.Add(new AssignmentLogic(
                ESharpParser.Expressions.RelationshipAssign,
                EntityElementType.RelationshipAssign,
                EntityElementType.RelationshipField));

            //to
            Assignments.Add(new AssignmentLogic(
                ESharpParser.Expressions.Identifier,
                EntityElementType.RelationshipAssignTo,
                EntityElementType.RelationshipAssign));
            
            //split
            Assignments.Add(new AssignmentLogic(
                ESharpParser.Expressions.RelationshipSplit,
                EntityElementType.RelationshipSplit,
                EntityElementType.RelationshipAssignTo));

            //as
            Assignments.Add(new AssignmentLogic(
                ESharpParser.Expressions.RelationshipAs,
                EntityElementType.RelationshipAs,
                EntityElementType.RelationshipAssignTo,
                EntityElementType.RelationshipEntity));

            //type
            Assignments.Add(new AssignmentLogic(
                ESharpParser.Expressions.RelationshipType,
                EntityElementType.RelationshipType,
                new AssignmentLogicHandler(AssignType),
                EntityElementType.RelationshipAs));

            //end
            Assignments.Add(new AssignmentLogic(
                ESharpParser.Expressions.StatementEnd,
                EntityElementType.StatementEnd,
                PostAssignmentAction.Stop,
                EntityElementType.RelationshipType));
        }

        private void AssignName(object sender, AssignmentLogicEventArgs e)
        {
            Name = e.Elements.Peek().Value;
        }

        private void AssignType(object sender, AssignmentLogicEventArgs e)
        {
            switch (e.Elements.Peek().Value)
            {
                case "parent":
                    Type = EntityRelationshipType.Parent;
                    break;
                case "child":
                    Type = EntityRelationshipType.Child;
                    break;
                case "sibling":
                    Type = EntityRelationshipType.Sibling;
                    break;
                default: 
                    Type = EntityRelationshipType.Unknown;
                    break;
            }
        }
    }
}
